package at.fhooe.stefanirndorfer.quadtree;

import static at.fhooe.stefanirndorfer.utilities.Utilities.NE;
import static at.fhooe.stefanirndorfer.utilities.Utilities.NW;
import static at.fhooe.stefanirndorfer.utilities.Utilities.SE;
import static at.fhooe.stefanirndorfer.utilities.Utilities.SW;
import at.fhooe.stefanirndorfer.collision.CollisionDetector;
import at.fhooe.stefanirndorfer.entity.EntityManager;
import at.fhooe.stefanirndorfer.game.Game;
import at.fhooe.stefanirndorfer.logging.Log;
import at.fhooe.stefanirndorfer.materials.Material;
import at.fhooe.stefanirndorfer.walls.Wall;

public class QuadTree_Manager {
	
	double totalTime;
	double collDetTime;
	
	int minSize;
	int maxSize;
	int maxWallPoints;
	QuadTreeNode rootNode;
	
	private EntityManager em;
	
	//Debug
	int leafCounter;
	
	public QuadTree_Manager(){
		rootNode = null;
	}
	
	public void initialize(int minSize, int maxSize, int maxWallPoints, double[] center){
		totalTime = 0.0;
		collDetTime = 0.0;
		
		this.minSize = minSize;
		this.maxSize = maxSize;
		this.maxWallPoints = maxWallPoints;
		
		rootNode = new QuadTreeNode(maxSize, center, null, (short)-1); //rootnode: sonType = -1
		em = Game.getInstance().getEntityManager();
		leafCounter = 0;
		
		//TimeMeasurements
		long startTime = System.nanoTime();
		
		//perform tree generation
		generateTree(rootNode);
		long endTime = System.nanoTime();
		totalTime = (endTime - startTime) / 1000000;
		Log.info(this, "Leaf-Counter: " + leafCounter);
		Log.info(this, "Total time in milliseconds: " + totalTime);
		collDetTime = collDetTime / 1000000;
		Log.info(this, "Collition detection time in milliseconds: " + collDetTime);
		
		
	}

	
	/**
	 * this method uses the entity-manager to perform collision detections
	 * 
	 * @param center
	 */
	public void generateTree(QuadTreeNode node){
		
		double[] upperLeft = new double[]{node.getCenter()[0] - node.getSize()/2, node.getCenter()[1] + node.getSize()/2};
		double[] lowerRight = new double[]{node.getCenter()[0] + node.getSize()/2, node.getCenter()[1] - node.getSize()/2};
		
		//time measurement
		long startTime = System.nanoTime();
		
		NodeCompositionData ncd = yieldNodeType(upperLeft, lowerRight);
		long endTime = System.nanoTime();
		collDetTime += endTime-startTime;
		
		switch(ncd.getType()){
		case 0:
			double size = node.getSize()/2;
			
			// sub-divide clockwise (NW,NE,SE,SW) and call generateTree
			
			//childnode NW
			QuadTreeNode childNW = new QuadTreeNode(size, new double[]{upperLeft[0] + size/2, upperLeft[1] - size/2}, node, NW);
			node.getChildNodes()[NW] = childNW;
			generateTree(childNW);
			
			//childnode NE
			QuadTreeNode childNE = new QuadTreeNode(size, new double[]{upperLeft[0] + size * 1.5, upperLeft[1] - size/2}, node, NE);
			node.getChildNodes()[NE] = childNE;
			generateTree(childNE);
			
			//childnode SE
			QuadTreeNode childSE = new QuadTreeNode(size, new double[]{upperLeft[0] + size * 1.5, upperLeft[1] - size * 1.5}, node, SE);
			node.getChildNodes()[SE] = childSE;
			generateTree(childSE);
			
			//childnode SW
			QuadTreeNode childSW = new QuadTreeNode(size, new double[]{upperLeft[0] + size/2, upperLeft[1] - size * 1.5}, node, SW);
			node.getChildNodes()[SW] = childSW;
			generateTree(childSW);
			
			break;
			
		case 1:
			leafCounter++;
			break;
			
		case 2:
			leafCounter++;
			node.setMaterial(ncd.getMaterial());
			break;
		}
		
	}
	
	
//	/**
//	 * AABB AABB
//	 * returns 1 if node is a room leaf,
//	 * returns 2 if node is a wall leaf,
//	 * returns 0 if node must be divided
//	 * 
//	 * @param upperLeft
//	 * @param lowerRight
//	 * @return
//	 */
//	private NodeCompositionData yieldNodeType(double[] upperLeft, double[] lowerRight) {
//		
//		int size = (int)Math.abs(lowerRight[0] - upperLeft[0]);
//		boolean divisible = size/2 >= minSize;
//		
//		
//		Wall w = em.checkBoxIntersection(upperLeft, lowerRight);
//		if(w != null){
//			//check if this node is totally included by a wall segment
//			if(CollisionDetector.isTotallyIncluded(w, upperLeft, lowerRight)){
//				return new NodeCompositionData((short)2, w.getM()); //a wall leaf
//			}else{
//				if(divisible){
//					return new NodeCompositionData((short)0, null);
//				}else{
//					// either a wall or a room leaf -- rather a room leaf
//					return new NodeCompositionData((short)2, w.getM()); //a wall leaf
//				}
//				//its mixed wall and room
//				//return stupidDetection(upperLeft, lowerRight, material, size, divisible);
//			}
//		}else{
//			return new NodeCompositionData((short)1, null); // a room leaf
//		}
//	}
	
	
	/**
	 * Hybrid 1
	 * returns 1 if node is a room leaf,
	 * returns 2 if node is a wall leaf,
	 * returns 0 if node must be divided
	 * 
	 * @param upperLeft
	 * @param lowerRight
	 * @return
	 */
	private NodeCompositionData yieldNodeType(double[] upperLeft, double[] lowerRight) {
		
		int size = (int)Math.abs(lowerRight[0] - upperLeft[0]);
		boolean divisible = size/2 >= minSize;
		
		
		Wall w = em.checkBoxIntersection(upperLeft, lowerRight);
		if(w != null){
			//check if this node is totally included by a wall segment
			if(CollisionDetector.isTotallyIncluded(w, upperLeft, lowerRight)){
				return new NodeCompositionData((short)2, w.getM()); //a wall leaf
			}else{
				if(divisible){
					return new NodeCompositionData((short)0, null);
				}else{
					return stupidDetection(upperLeft, lowerRight, w.getM(), size, divisible);
				}
				//its mixed wall and room
				//return stupidDetection(upperLeft, lowerRight, material, size, divisible);
			}
		}else{
			return new NodeCompositionData((short)1, null); // a room leaf
		}
	}
//	/**
//		
//		int size = (int)Math.abs(lowerRight[0] - upperLeft[0]);
//		boolean divisible = size/2 >= minSize;
//		
//		
//		Wall w = em.checkBoxIntersection(upperLeft, lowerRight);
//		if(w != null){
//			//check if this node is totally included by a wall segment
//			if(CollisionDetector.isTotallyIncluded(w, upperLeft, lowerRight)){
//				return new NodeCompositionData((short)2, w.getM()); //a wall leaf
//			}else{
//				if(divisible){
//					return new NodeCompositionData((short)0, null);
//				}else{
//					// either a wall or a room leaf -- rather a room leaf
//					//return new NodeCompositionData((short)2, w.getM()); //a wall leaf
//					
//					//hybrid version
//					return stupidDetection(upperLeft, lowerRight, w.getM(), size, divisible);
//				}
//				//its mixed wall and room
//				//return stupidDetection(upperLeft, lowerRight, material, size, divisible);
//			}
//		}else{
//			return new NodeCompositionData((short)1, null); // a room leaf
//		}
//	}
	
	
	
//	/**
//	 * Hybrid2
//	 * returns 1 if node is a room leaf,
//	 * returns 2 if node is a wall leaf,
//	 * returns 0 if node must be divided
//	 * 
//	 * @param upperLeft
//	 * @param lowerRight
//	 * @return
//	 */
//	private NodeCompositionData yieldNodeType(double[] upperLeft, double[] lowerRight) {
//		
//		int size = (int)Math.abs(lowerRight[0] - upperLeft[0]);
//		boolean divisible = size/2 >= minSize;
//		
//		
//		Wall w = em.checkBoxIntersection(upperLeft, lowerRight);
//		if(w != null){
//			//check if this node is totally included by a wall segment
//			if(CollisionDetector.isTotallyIncluded(w, upperLeft, lowerRight)){
//				return new NodeCompositionData((short)2, w.getM()); //a wall leaf
//			}else{
//					//hybrid version
//					return stupidDetection(upperLeft, lowerRight, w.getM(), size, divisible);
//			}
//		}else{
//			return new NodeCompositionData((short)1, null); // a room leaf
//		}
//	}
	
	
	

//	/**
//	 * stupid version
//	 * @param upperLeft
//	 * @param lowerRight
//	 * @return
//	 */
//	private NodeCompositionData yieldNodeType(double[] upperLeft, double[] lowerRight) {
//		int size = (int)Math.abs(lowerRight[0] - upperLeft[0]);
//		boolean divisible = size/2 >= minSize;
//		Material material = null;
//		int wallPointCounter = 0;
//		int nonWallPointCounter = 0;
//		
//		int uStartCoord = (int)upperLeft[0];
//		int vStartCoord = (int)lowerRight[1];
//		
//		for (int u = uStartCoord; u < size + uStartCoord; u++) {
//			for (int v = vStartCoord; v < size + vStartCoord; v++) {
//				
//				if(em.checkWallCollisions(u,v) != null){
//					if(material == null)
//						material = em.checkWallCollisions(u, v).getM();
//					wallPointCounter++;					
//				}else{
//					nonWallPointCounter++;
//				}
//				
//				if(wallPointCounter > maxWallPoints && !divisible){
//					return new NodeCompositionData((short)2, material); //a wall leaf
//				}
//				if(nonWallPointCounter > maxWallPoints && !divisible){
//					return new NodeCompositionData((short)1, null); // a room leaf
//				}
//				if((wallPointCounter > maxWallPoints && nonWallPointCounter > maxWallPoints) && divisible){
//					return new NodeCompositionData((short)0, null); // can be divided, mixed wall/room
//				}
//			}
//}
//		
//		// a leaf of bigger size than minSize, which is either a wall leaf or a room leaf
//		if(wallPointCounter >= nonWallPointCounter){
//			return new NodeCompositionData((short)2, material); //a wall leaf
//		}else{
//			return new NodeCompositionData((short)1, null); // a room leaf
//		}
//		
//	}

	
	/**
	 * @param upperLeft
	 * @param lowerRight
	 * @param material
	 * @param size
	 * @param divisible
	 * @return
	 */
	private NodeCompositionData stupidDetection(double[] upperLeft,
			double[] lowerRight, Material material, int size, boolean divisible) {
		int wallPointCounter = 0;
		int nonWallPointCounter = 0;
		
		int uStartCoord = (int)upperLeft[0];
		int vStartCoord = (int)lowerRight[1];
		
		for (int u = uStartCoord; u < size + uStartCoord; u++) {
			for (int v = vStartCoord; v < size + vStartCoord; v++) {
				
				if(em.checkWallCollisions(u,v) != null){
					if(material == null)
						material = em.checkWallCollisions(u, v).getM();
					wallPointCounter++;					
				}else{
					nonWallPointCounter++;
				}
				
				if(wallPointCounter > maxWallPoints && !divisible){
					return new NodeCompositionData((short)2, material); //a wall leaf
				}
				if(nonWallPointCounter > maxWallPoints && !divisible){
					return new NodeCompositionData((short)1, null); // a room leaf
				}
				if((wallPointCounter > maxWallPoints && nonWallPointCounter > maxWallPoints) && divisible){
					return new NodeCompositionData((short)0, null); // can be divided, mixed wall/room
				}
			}
		}
		
		// a leaf of bigger size than minSize, which is either a wall leaf or a room leaf
		if(wallPointCounter >= nonWallPointCounter){
			return new NodeCompositionData((short)2, material); //a wall leaf
		}else{
			return new NodeCompositionData((short)1, null); // a room leaf
		}
	}


	//////////////////////////////////////////////////
	// Getters and Setters
	//////////////////////////////////////////////////
	
	
	
	public int getMaxSize() {
		return maxSize;
	}

	public int getMinSize() {
		return minSize;
	}


	public void setMinSize(int minSize) {
		this.minSize = minSize;
	}


	public void setMaxSize(int maxSize) {
		this.maxSize = maxSize;
	}

	public int getAnchorInPixel() {
		return maxWallPoints;
	}

	public void setAnchorInPixel(int anchorInPixel) {
		this.maxWallPoints = anchorInPixel;
	}


	public QuadTreeNode getRootNode() {
		return rootNode;
	}

	public void recalculateQuadtree(QuadTreeNode n) {
		generateTree(n);
	}
	
	
	
}

